package com.min.redis.common.util.jedis;

import com.min.redis.common.util.LogUtils;
import com.min.redis.common.util.redis.LuaTypeEnum;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.resps.Tuple;
import redis.clients.jedis.util.KeyValue;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;

/**
 * Jedis 工具类
 */
@Component
public class JedisUtil {
    @Getter
    protected static JedisPool jedisPool;
    @Resource
    private JedisProperties jedisProperties;

    private JedisUtil() {
    }

    @PostConstruct
    public void init() {
        LogUtils.COMMON.info("{}",jedisProperties);
        jedisPool = new JedisPool(jedisProperties.getHost(), jedisProperties.getPort(), jedisProperties.getDatabase(), jedisProperties.getPassword());
    }

    /**
     * 简单的Get
     *
     * @param key
     * @return
     */
    public static String get(String key) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.get(key);
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.get异常", e);
        }
        return null;
    }

    /**
     * 缓存value
     *
     * @param key
     * @param value
     * @param time
     * @return
     */
    public static boolean set(String key, Object value, long time) {
        return set(key, String.valueOf(value), time);
    }

    public static boolean set(String key, String value, long time) {
        if (time <= 0) {
            return set(key, value);
        }
        try {
            evalSha(key, LuaTypeEnum.SET_WITH_EXPIRE, value, String.valueOf(time));
            return true;
        } catch (Throwable e) {
            LogUtils.ERROR.error("jedis.set异常", e);
        }
        return false;
    }

    /**
     * 简单的set
     *
     * @param key   redis key
     * @param value redis value
     */
    public static boolean set(String key, String value) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            jds.set(key, value);
            return true;
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.set异常", e);
        }
        return false;
    }

    /**
     * 适配方法
     *
     * @param key         redis key
     * @param luaTypeEnum 对应lua脚本
     * @param params      lua脚本参数
     * @return 执行结果
     */
    public static Object evalSha(String key, LuaTypeEnum luaTypeEnum, String... params) {
        LogUtils.COMMON.info("RedisLuaHandler evalSha key:{}, LuaTypeEnum:{}, params:{}", key, luaTypeEnum, params);
        List<String> paramList = new ArrayList<>(Arrays.asList(params));
        try {
            return JedisUtil.eval(luaTypeEnum.getScript(), Collections.singletonList(key), paramList);
        } catch (Exception e) {
            LogUtils.COMMON.error("错误信息为:", e);
        }
        return null;
    }

    public static Object eval(String lua, List<String> keyList, List<String> param) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.eval(lua, keyList, param);
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.eval异常，lua :{}", lua, e);
        }
        return null;
    }

    /**
     * 判断缓存是否存在
     *
     * @param key
     * @return
     */
    public static boolean containsKey(String key) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.exists(key);
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.containsKey异常, key:{}", key, e);
        }
        return false;
    }


    /**
     * 判断缓存是否存在
     *
     * @param key
     * @return
     */
    public static boolean del(String key) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            jds.del(key);
            return true;
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.del异常:{}", key, e);
        }
        return false;
    }


    /**
     * 判断缓存是否存在
     * HashGet
     * 从key集合中到item字段取出value
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public static String hget(String key, String item) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.hget(key, item);
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.hget异常", e);
        }
        return null;
    }

    /**
     * 获取hashkey对应的item的值
     *
     * @param key
     * @param items
     * @return
     */
    public static List<String> hmget(String key, String[] items) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            List<String> hmget = jds.hmget(key, items);
            return hmget;
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.hget异常", e);
        }
        return null;
    }

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public static boolean expire(String key, long time) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            if (time > 0) {
                jds.expire(key, time);
            }
            return true;
        } catch (Exception e) {
            LogUtils.ERROR.error("设置过期失败, key = {}, ttl = {}", key, time, e);
            return false;
        }
    }


    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public static boolean hmset(String key, Map<String, String> map) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            jds.hmset(key, map);
            return true;
        } catch (Exception e) {
            LogUtils.ERROR.error("设置hmset失败, key = {}", key, e);
            return false;
        }
    }


    /**
     * HashSet
     *
     * @param key  键
     * @param map  对应多个键值
     * @param flag 代表刷新时间
     * @return true 成功 false 失败
     */
    public static void hmset(String key, Map<String, String> map, int timeout, boolean flag) {
        try {
            hmset(key, map);
            if (flag || !containsKey(key)) {
                expire(key, timeout);
            }
        } catch (Exception e) {
            LogUtils.ERROR.error("设置hmset失败, key = {}", key, e);
        }
    }


    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public static Map<String, String> hgetAll(String key) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.hgetAll(key);
        } catch (Exception e) {
            LogUtils.ERROR.error("设置hgetAll失败, key = {}", key, e);
        }
        return null;
    }

    /**
     * hash结构中若存在相应key才进行操作
     * key集合中没有hashkey 则不操作
     *
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public static boolean hsetnx(String key, String hashKey, String value) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            jds.hsetnx(key, hashKey, value);
            return true;
        } catch (Exception e) {
            LogUtils.ERROR.error("设置hsetnx失败, key = {}", key, e);
        }
        return false;
    }

    /**
     * 获取哈希列表中字段数量
     *
     * @param key
     * @return
     */
    public static long hlen(String key) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.hlen(key);
        } catch (Exception e) {
            LogUtils.ERROR.error("设置hlen失败, key = {}", key, e);
        }
        return 0L;
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public static void hdel(String key, String... item) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            jds.hdel(key, item);
        } catch (Exception e) {
            LogUtils.ERROR.error("设置hdel失败, key = {}", key, e);
        }
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public static boolean exists(String key) {
        try {
            return containsKey(key);
        } catch (Exception e) {
            LogUtils.ERROR.error("exists失败, key = {}", key, e);
        }
        return false;
    }


    /**
     * 累加
     *
     * @param key
     */
    public static Long incr(String key) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.incr(key);
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.incr异常", e);
        }
        return null;
    }

    /**
     * 简单decr
     *
     * @param key 减少数据
     * @return 当前数据
     */
    public static Long decr(String key) {
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.decr(key);
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.decr异常", e);
        }
        return null;
    }


    /**
     * 设置过期时间
     */
    private static final String SET_EXP = "1";
    /**
     * 不设置过期时间
     */
    private static final String UN_SET_EXP = "0";

    /**
     * 累加且设置超时时间,且是否刷新过期时间
     * <p>
     * Params:
     * key - the key
     * seconds - the seconds type: long
     * forceRefresh - always refresh expire time else only set expire time when key is not exist
     * 始终刷新过期时间，否则仅在密钥不存在时设置过期时间
     *
     * @param key
     * @param timeout
     * @param flag
     */
    public static Long incr(String key, int timeout, boolean flag) {
        LogUtils.COMMON.info("使用incr参数 key:{}, timeout:{}, flag: {}", key, timeout, flag);
        String param = UN_SET_EXP;
        if (flag) {
            param = SET_EXP;
        }
        Object result = JedisUtil.evalSha(key, LuaTypeEnum.INCR, param, String.valueOf(timeout));
        if (result == null) {
            return null;
        }
        return Long.parseLong(result.toString());
    }

    /**
     * 缓存数值
     * <p>
     * <p>
     * Params:
     * key - the key
     * value - the value
     * nxxx - NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key if it already exist.
     * expx - EX|PX, expire time units: EX = seconds; PX = milliseconds
     * time - expire time in the units of expx
     *
     * @param key     键
     * @param value   值
     * @param nx      NX—仅当密钥不存在时才设置该密钥。XX——只有在密钥已经存在的情况下才设置密钥
     * @param ex      过期时间单位：EX=秒；PX=毫秒
     * @param timeout 超时时间 以expx为单位的过期时间
     * @return
     */
    public static String set(String key, String value, String nx, String ex, Long timeout) {
        //从原逻辑中迁移过来，增加判断以防无用
        if (StringUtils.equals(nx, "XX")) {
            throw new RuntimeException("暂不支持XX");
        }
        Object result = JedisUtil.evalSha(key, LuaTypeEnum.CLUSTER_LOCK, value, ex, String.valueOf(timeout));
        if (result == null) {
            return null;
        }
        int res = Integer.parseInt(result.toString());
        if (res == 0) {
            return "FAIL";
        }
        return "OK";
    }

    /**
     * 管道批量查询key 批量查询可以有效减少与 Redis 的通信次数，提高性能
     *
     * @param routeKey 用于将查询路由到指定的 Redis 节点，通常用于分片或集群场景（目前不会做分片集群） 没作用
     * @param keys     查询的多个键的列表
     * @param o        没用到
     * @param exec     是否开启事务 没用到
     * @param index    数据库索引
     */
    public static Map<String, String> pipelineResult(String routeKey, List<String> keys, Object o, boolean exec, int index) {
        LogUtils.COMMON.debug("参数：routekey:{},keys:{},o{},exec{},index{}", routeKey, keys, o, exec, index);
        if (CollectionUtils.isEmpty(keys)) {
            LogUtils.ERROR.error("键列表为空");
            return null;
        }
        Map<String, String> result = new HashMap<>();
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            Pipeline pipelined = jds.pipelined();
            for (String key : keys) {
                pipelined.get(key);
            }
            List<Object> resList = pipelined.syncAndReturnAll();
            for (int i = 0; i < resList.size(); i++) {
                result.put(keys.get(i), String.valueOf(resList.get(i)));
            }
        } catch (Exception e) {
            LogUtils.ERROR.error("jedis.pipelineResult异常", e);
        }
        return result;
    }

    /*########################  zset(有序集合)的操作  #######################*/

    /**
     * zet添加一个成员
     * @param key
     * @param score
     * @param member
     * @return
     */
    public static Long zaddOne(String key,double score, String member){
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.zadd(key, score, member);
        }catch (Exception e){
            LogUtils.ERROR.error("jedis.zaddOne异常",e);
        }
        return null;
    }

    /**
     * zset 批量添加
     * @param key
     * @param memberWithScore
     * @return
     */
    public static Long zaddBatch(String key, Map<String,Double> memberWithScore){
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.zadd(key, memberWithScore);
        }catch (Exception e){
            LogUtils.ERROR.error("jedis.zaddBatch异常",e);
        }
        return null;
    }

    /**
     *
     * @param key
     * @param member
     * @param increment
     * @return
     */
    public static Double zincrby(String key,String member,double increment){
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.zincrby(key, increment, member);
        }catch (Exception e){
            LogUtils.ERROR.error("jedis.zincrby异常",e);
        }
        return null;
    }

    /**
     *
     * @param key
     * @param member
     * @return
     */
    public static Long zremone(String key,String member){
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.zrem(key,member);
        }catch (Exception e){
            LogUtils.ERROR.error("jedis.zremone异常",e);
        }
        return null;
    }



    /**
     * 查询zset指定成员及其分数(倒序)
     * @param key
     * @param start
     * @param stop
     * @return
     */
    public static List<Tuple> zrevrangeWithScores(String key,long start,long stop){
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.zrevrangeWithScores(key, start,stop);
        }catch (Exception e){
            LogUtils.ERROR.error("jedis.zrevrangeWithScores异常",e);
        }
        return null;
    }

    /**
     * 查询zset所有成员及其分数(倒序)
     * @param key
     * @return
     */
    public static List<Tuple> zrevrangeAllWithScores(String key){
        return zrevrangeWithScores(key,0,-1);
    }

    /**
     *  查询排名(倒序)
     * @param key
     * @param member
     * @return
     */
    public static Long zrevrank(String key,String member){
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.zrevrank(key,member);
        }catch (Exception e){
            LogUtils.ERROR.error("jedis.zrevrank异常",e);
        }
        return null;
    }
    /**
     * 查询排名和分数(倒序)
     *
     * @param key
     * @param member
     * @return
     */
    public static KeyValue<Long, Double> zrevrankWithScore(String key, String member){
        try (Jedis jds = jedisPool.getResource()) {
            jds.select(0);
            return jds.zrevrankWithScore(key,member);
        }catch (Exception e){
            LogUtils.ERROR.error("jedis.zrevrankWithScore异常",e);
        }
        return null;
    }

    public static Long zunion(String dstKey,List<String> members){
        try(Jedis jds = jedisPool.getResource()){
            jds.select(0);
            return jds.zunionstore(dstKey,members.toArray(new String[0]));
        }catch (Exception e){
            LogUtils.ERROR.error("jedis.zunion异常",e);
        }
        return null;
    }

}
